// 版权所有2011 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package des

import (
	"encoding/binary"
	"sync"
)

func cryptBlock(subkeys []uint64, dst, src []byte, decrypt bool) {
	b := binary.BigEndian.Uint64(src)
	b = permuteInitialBlock(b)
	left, right := uint32(b>>32), uint32(b)

	left = (left << 1) | (left >> 31)
	right = (right << 1) | (right >> 31)

	if decrypt {
		for i := 0; i < 8; i++ {
			left, right = feistel(left, right, subkeys[15-2*i], subkeys[15-(2*i+1)])
		}
	} else {
		for i := 0; i < 8; i++ {
			left, right = feistel(left, right, subkeys[2*i], subkeys[2*i+1])
		}
	}

	left = (left << 31) | (left >> 1)
	right = (right << 31) | (right >> 1)

	// 左右切换并执行最终置换
	preOutput := (uint64(right) << 32) | uint64(left)
	binary.BigEndian.PutUint64(dst, permuteFinalBlock(preOutput))
}

// 使用子密钥将src中的一个块加密到dst中。
func encryptBlock(subkeys []uint64, dst, src []byte) {
	cryptBlock(subkeys, dst, src, false)
}

// 使用子密钥将src中的一个块解密到dst中。
func decryptBlock(subkeys []uint64, dst, src []byte) {
	cryptBlock(subkeys, dst, src, true)
}

// DES Feistel函数。feistelBox必须通过
// feistelBoxOnce初始化。先做（初始化）。
func feistel(l, r uint32, k0, k1 uint64) (lout, rout uint32) {
	var t uint32

	t = r ^ uint32(k0>>32)
	l ^= feistelBox[7][t&0x3f] ^
		feistelBox[5][(t>>8)&0x3f] ^
		feistelBox[3][(t>>16)&0x3f] ^
		feistelBox[1][(t>>24)&0x3f]

	t = ((r << 28) | (r >> 4)) ^ uint32(k0)
	l ^= feistelBox[6][(t)&0x3f] ^
		feistelBox[4][(t>>8)&0x3f] ^
		feistelBox[2][(t>>16)&0x3f] ^
		feistelBox[0][(t>>24)&0x3f]

	t = l ^ uint32(k1>>32)
	r ^= feistelBox[7][t&0x3f] ^
		feistelBox[5][(t>>8)&0x3f] ^
		feistelBox[3][(t>>16)&0x3f] ^
		feistelBox[1][(t>>24)&0x3f]

	t = ((l << 28) | (l >> 4)) ^ uint32(k1)
	r ^= feistelBox[6][(t)&0x3f] ^
		feistelBox[4][(t>>8)&0x3f] ^
		feistelBox[2][(t>>16)&0x3f] ^
		feistelBox[0][(t>>24)&0x3f]

	return l, r
}

// feistelBox[s][16*i+j]包含置换函数
// 对于sBoxes[s][i][j]<<4*（7-s）
var feistelBox [8][64]uint32

var feistelBoxOnce sync.Once

// 执行DES块置换的通用函数
func permuteBlock(src uint64, permutation []uint8) (block uint64) {
	for position, n := range permutation {
		bit := (src >> n) & 1
		block |= bit << uint((len(permutation)-1)-position)
	}
	return
}

func initFeistelBox() {
	for s := range sBoxes {
		for i := 0; i < 4; i++ {
			for j := 0; j < 16; j++ {
				f := uint64(sBoxes[s][i][j]) << (4 * (7 - uint(s)))
				f = permuteBlock(f, permutationFunction[:])

				// 行由第1位和第6位确定。
				// 列是中间的四位。
				row := uint8(((i & 2) << 4) | i&1)
				col := uint8(j << 1)
				t := row | col

				// 旋转是在feistel回合中执行的，经过分解，现在混合到feistel框中。
				f = (f << 1) | (f >> 31)

				feistelBox[s][t] = uint32(f)
			}
		}
	}
}

// 置换初始块等价于初始置换定义的置换
// 置换。
func permuteInitialBlock(block uint64) uint64 {
	// block=b7 b6 b5 b4 b3 b2 b1 b0（8字节）
	b1 := block >> 48
	b2 := block << 48
	block ^= b1 ^ b2 ^ b1<<48 ^ b2>>48

	// block=b1 b0 b5 b4 b3 b2 b7 b6 
	b1 = block >> 32 & 0xff00ff
	b2 = (block & 0xff00ff00)
	block ^= b1<<32 ^ b2 ^ b1<<8 ^ b2<<24 // 用b3 b7 

	// block现在是b1 b3 b5 b7 b7 b4 b4 b6，排列方式是：
	// /。。。8 
	// /。。。24 
	// /。。。40 
	// /。。。56 
	// 7 6 5 4 3 2 1 0 
	// 23 22 21 20 19 18 16 
	// 。。。32 
	// /。。。48 

	// 与32,33,34,35等交换4,5,6,7。
	b1 = block & 0x0f0f00000f0f0000
	b2 = block & 0x0000f0f00000f0f0
	block ^= b1 ^ b2 ^ b1>>12 ^ b2<<12

	// 块是排列：
	// 
	// 
	// /23 22 21 20 

	b1 = block & 0x3300330033003300
	b2 = block & 0x00cc00cc00cc00cc
	block ^= b1 ^ b2 ^ b1>>6 ^ b2<<6

	// block是排列：
	// 15 14 
	// 13 12 
	// 11 10 
	// 7 6 

	b1 = block & 0xaaaaaaaa55555555
	block ^= b1 ^ b1>>33 ^ b1<<33

	// 614230238465462 
	// 412202836445260 
	// 2108264425058 
	// 0816204044856 
	// 715331395563 
	// 311192735159 
	return block
}

// 。
func permuteFinalBlock(block uint64) uint64 {
	// 执行与永久初始块
	// 相同的位交换，但顺序相反。
	b1 := block & 0xaaaaaaaa55555555
	block ^= b1 ^ b1>>33 ^ b1<<33

	b1 = block & 0x3300330033003300
	b2 := block & 0x00cc00cc00cc00cc
	block ^= b1 ^ b2 ^ b1>>6 ^ b2<<6

	b1 = block & 0x0f0f00000f0f0000
	b2 = block & 0x0000f0f00000f0f0
	block ^= b1 ^ b2 ^ b1>>12 ^ b2<<12

	b1 = block >> 32 & 0xff00ff
	b2 = (block & 0xff00ff00)
	block ^= b1<<32 ^ b2 ^ b1<<8 ^ b2<<24

	b1 = block >> 48
	b2 = block << 48
	block ^= b1 ^ b2 ^ b1<<48 ^ b2>>48
	return block
}

// 创建16个根据
// 旋转计划
func ksRotate(in uint32) (out []uint32) {
	out = make([]uint32, 16)
	last := in
	for i := 0; i < 16; i++ {
		// 28位循环左移
		left := (last << (4 + ksRotations[i])) >> 4
		right := (last << 4) >> (32 - ksRotations[i])
		out[i] = left | right
		last = out[i]
	}
	return
}

// 从原始密钥
func (c *desCipher) generateSubkeys(keyBytes []byte) {
	feistelBoxOnce.Do(initFeistelBox)

	// 将PC1置换应用于密钥
	key := binary.BigEndian.Uint64(keyBytes)
	permutedKey := permuteBlock(key, permutedChoice1[:])

	// 根据旋转计划
	leftRotations := ksRotate(uint32(permutedKey >> 28))
	rightRotations := ksRotate(uint32(permutedKey<<4) >> 4)

	// 生成子键
	for i := 0; i < 16; i++ {
		// 组合两半，形成PC2的56位输入
		pc2Input := uint64(leftRotations[i])<<28 | uint64(rightRotations[i])
		// 将PC2置换应用于7字节输入
		c.subkeys[i] = unpack(permuteBlock(pc2Input, permutedChoice2[:]))
	}
}

// 将48位输入扩展到64位，每个6位块顶部额外填充两位。
// 这样，我们可以让输入块（每个四位）和关键块（每个六位）很好地对齐，而不需要额外的移位/旋转来对齐。
func unpack(x uint64) uint64 {
	var result uint64

	result = ((x>>(6*1))&0xff)<<(8*0) |
		((x>>(6*3))&0xff)<<(8*1) |
		((x>>(6*5))&0xff)<<(8*2) |
		((x>>(6*7))&0xff)<<(8*3) |
		((x>>(6*0))&0xff)<<(8*4) |
		((x>>(6*2))&0xff)<<(8*5) |
		((x>>(6*4))&0xff)<<(8*6) |
		((x>>(6*6))&0xff)<<(8*7)

	return result
}
